package hn.cch.grammar.security;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RsaUtil {


    private static final String algorithm = "RSA";

    private static final int keylen = 2048;

    public static KeyPair keyPair(int keylen) {
        KeyPair keyPair = null;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
            keyPairGenerator.initialize(keylen);
            keyPair = keyPairGenerator.generateKeyPair();

        } catch (NoSuchAlgorithmException e) {
            System.out.println(e.getMessage());
        }
        return keyPair;
    }

    public enum CRYPT {
        EN,// 加密
        DE// 解密
    }

    /**
     * @param crypt   1：加密  2：解密
     * @param data    数据
     * @param keyPair 密钥对
     * @param keylen  密钥长度
     * @return
     */
    public static byte[] doCrypt(CRYPT crypt, byte[] data, KeyPair keyPair, int keylen) {
        byte[] temp = null;
        try {
            // 密钥加算法
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
            Cipher cipher = Cipher.getInstance(algorithm);

            // 加密或解密
            switch (crypt) {
                case EN:
                    // 公钥加密
                    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyFactory.generatePublic(
                            new X509EncodedKeySpec(keyPair.getPublic().getEncoded()));
                    cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
                    break;
                case DE:
                    // 私钥解密
                    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyFactory.generatePrivate(
                            new PKCS8EncodedKeySpec(keyPair.getPrivate().getEncoded()));
                    cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
                    break;
                default:
                    System.out.println("crypt error : CRYPT.EN|CRYPT.DE");
            }
            // 数据处理
            temp = doFinal(cipher, crypt, data, keylen);
            // 异常处理
        } catch (InvalidKeyException e) {
            System.out.println(e.getMessage());
            System.out.println(e.getMessage());
        } catch (InvalidKeySpecException e) {
            System.out.println(e.getMessage());
        } catch (NoSuchAlgorithmException e) {
            System.out.println(e.getMessage());
        } catch (NoSuchPaddingException e) {
            System.out.println(e.getMessage());
        }
        return temp;
    }


    public static byte[] doFinal(Cipher cipher, CRYPT crypt, byte[] data, int keylen) {

        int length = 0;
        switch (crypt) {
            case EN:
                length = keylen / 8 - 11;
                break;
            case DE:
                length = keylen / 8;
                break;
            default:
                length = 0;
        }
        int offset = 0;
        byte[] buffer = new byte[0];
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        try {
            for (int i = 0; data.length > offset; i++) {
                if (data.length - offset > length) {
                    buffer = cipher.doFinal(data, offset, length);
                } else {
                    buffer = cipher.doFinal(data, offset, data.length - offset);
                }
                stream.write(buffer, 0, buffer.length);
                offset = i * length;
            }
        } catch (IllegalBlockSizeException e) {
            System.out.println(e.getMessage());
        } catch (BadPaddingException e) {
            System.out.println(e.getMessage());
        }
        byte[] result = stream.toByteArray();
        try {
            stream.close();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
        return result;
    }


    /**
     * 根据模数modulus(n)、指数exponent(e)生成公钥
     *
     * @param modulus
     * @param exponents
     * @return
     */
    public PublicKey getPublicKey(String modulus, String exponents) {
        try {
            RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(
                    new BigInteger(modulus), new BigInteger(exponents));
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
            PublicKey publicKey = keyFactory.generatePublic(rsaPublicKeySpec);
            return publicKey;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return null;
        }
    }

    /**
     * base64解密密钥生成公钥
     *
     * @param encodedKey
     * @return
     */
    public PublicKey getPublicKey(String encodedKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
            PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(
                    Base64Util.decode(encodedKey.getBytes(StandardCharsets.UTF_8))));
            return publicKey;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return null;
        }
    }


    public static void main(String[] args) {

        int keylen = 2048;
        byte[] data = "1234567890qwertyuiopasdfghjklzxcvbnm".getBytes(StandardCharsets.UTF_8);
        // KeyPair keyPair = RsaUtil.keyPair(keylen);


        // logger.info(Base64.encodeBase64URLSafeString((new X509EncodedKeySpec(keyPair.getPublic().getEncoded()).getEncoded())));
        // logger.info(Base64Util.encodeBuffer((new X509EncodedKeySpec(keyPair.getPublic().getEncoded()).getEncoded())));
        // logger.info(BytesUtil.toHex(keyPair.getPublic().getEncoded()));

        // byte[] temp = RsaUtil.doCrypt(RsaUtil.CRYPT.EN, data, keyPair, keylen);
        // logger.info(Base64Util.encodeBuffer(temp));
        //
        // byte[] info = RsaUtil.doCrypt(RsaUtil.CRYPT.DE, data, keyPair, keylen);
        // logger.info(new String(temp));
    }

}

